[情境劇場]
解師傅:餐廳請了歌手來駐唱,讓生意越來越好了,卻引來了隔壁小吃店的不滿,在我們版上留了負評
小當家:人紅是非多…這也是沒辦法的事,金錢才是王道啊!!
在介紹 useEffect 先來認識什麼是 Effect,Effect 指的是「Side Effect
」,簡稱 Effect
,中文是副作用的意思。
我們最常聽到的副作用,也就是醫生開藥的藥單上面都會寫哪些藥,上面寫著作用跟副作用,作用是緩解鼻塞、流鼻水,可能會伴隨的副作用是會想睡覺
在 JavaScript 的也是這樣的存在,「在執行函式或行為時,會導致原有的狀態被改變或有附加功能」,就稱之為 Side Effect,例如:改變全域變數狀態、發送 HTTP Request、手動操作 DOM 元素 、改變系統狀態等等
useEffect 就是來處理 Side Effect 的 Hook,早期的 Class Component 是使用生命週期 (lifecycle)來管理組件函式,但這讓相同邏輯的函式,被迫拆開在不同的生命週期
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
因此 useEffect 的設計在將其保持在一起,易於管理,在程式碼上也較簡潔許多
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
import { useEffect } from 'react';
useEffect(() => {
//...要執行的 effect
})
useEffect 會在參數中帶入一個函式,而這個函式會在「畫面渲染完成」後被呼叫,函式裡面放入畫面完成後需要執行的 effect
useEffect(() => {
//...要執行的 effect
}, [dependencies])
useEffect 第二個參數為一個陣列,陣列裡會放入需要重新渲染 effect 的依賴,我們稱之為 dependencies,如 dependencies 有變動,才會重新執行 effect
這是一個單純的計數器功能,一開始畫面渲染完畢後,會接著執行 useEffect,執行結束後,一直到變更了 count 的值,才會再執行一次 useEffect
import { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("useEffect");
}, [count]);
return (
<div>
<h1>Count: {count}</h1>
<button
onClick={() => {
setCount((state) => state + 1);
}}
>
Add Count
</button>
</div>
);
}
為什麼畫面渲染完畢後,會跑了兩次 console.log?
你可以點擊 src/index.js 檔案,會看到裡面有 Strict Mode
嚴格模式
root.render(
<StrictMode>
<App />
</StrictMode>
);
Strict Mode 在開發模式下,為了檢測到渲染期生命週期的預期之外的 Side Effect,故意調用函式兩次,來幫助我們發現 Side Effect
這些函式有
constructor
、render
和 shouldComponentUpdate
方法getDerivedStateFromProps
方法setState
的第一個參數)useState
、useMemo
或 useReducer
如果我們在 index.js 下了 console.log(”index.js”),會發現 index.js 只渲染了一次
問題就很清楚了,Strict Mode 的開發模式下確實會渲染兩次
Strict Mode 只會在開發模式中執行,故不會調用在正式環境
useEffect(() => {
console.log("mounted");
}, [])
不依賴於任何 props 或 state 的值,不需要重新執行的函式或行為,可以直接傳遞一個空陣列
如果你有學過 Class Component,就會像是 lifecycle 中的 componentDidMount
useEffect(() => {
console.log("updated");
})
只要任何組件發生變動就會執行,會像是 class lifecycle 中的 componentDidUpdate
useEffect(() => {
console.log("updated with dependencies");
}, [count])
dependencies 發生改變才執行,如範例中的 count 如有變更,才會觸發 console.log,避免在每次 render 都進行昂貴的計算
清理函式會在組件每次重新渲染時執行,先清除上次留下來的 effect,useEffect 會做的 4 個步驟:
useEffect
的內容useEffect(() => {
const onMousedown = () => {
console.log("mousedown");
};
window.addEventListener("mousedown", onMousedown);
const timer = setInterval(() => {
console.log('Hello React');
}, 1000);
return () => {
console.log("cleanup");
window.removeEventListener("mousedown", onMousedown);
clearInterval(timer);
};
});
在 useEffect 函式裡 return
一個函式,這個函式就是清除 effect 的函式,就像是 class lifecycle 中的 componentWillUnmount
,如果沒有將 effect 清除,當組件重新渲染,都會執行一個新的事件監聽,這很可能會發生錯誤,也會讓效能下降,常見的需要清除的函式如:setInterval、setTimeout、addEventListener…等
認識了 useEffect 跟使用方法,讓我們得以處理 side effect,useEffect 跟之前的 class lifecycle 相比好寫很多,把相同邏輯放在一起也舒服很多~一起練習看看吧!
本文將同步更新至我的部落格
Lala 的前端大補帖